<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>黄小宝</title>
    <link rel="icon" type="image/svg+xml"
          href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='%23007AFF' d='M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z'/></svg>">
    <style>
        body {
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: #222;
            min-height: 100vh;
            overflow: hidden;
            display: flex;
            justify-content: center;
        }

        .bg-blur {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            z-index: 0;
            background: url('avatar.png') center/cover no-repeat;
            filter: blur(32px) brightness(0.7);
            width: 100vw;
            height: 100vh;
        }

        .main-content {
            position: relative;
            z-index: 1;
            height: 100vh;
            max-width: 600px;
            width: 100%;
        }

        .top-bar {
            width: 100%;
            display: flex;
            justify-content: flex-end;
            align-items: center;
            padding: 24px 20px 0 20px;
            gap: 18px;
            box-sizing: border-box;
        }

        .top-bar .icon-btn {
            background: rgba(255, 255, 255, 0.15);
            border: none;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
        }

        .top-bar .icon-btn:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: scale(1.05);
        }

        .top-bar .icon-btn:active {
            transform: scale(0.95);
        }

        .top-bar .icon-btn.active {
            background: rgba(0, 234, 255, 0.25);
        }

        .top-bar .icon-btn.active svg {
            filter: drop-shadow(0 0 8px rgba(0, 234, 255, 0.5));
        }

        .top-bar .icon-btn svg {
            transition: transform 0.3s ease, filter 0.3s ease;
        }

        .top-bar .icon-btn:hover svg {
            transform: scale(1.1);
        }

        .avatar-section {
            position: absolute;
            top: 18%;
            left: 50%;
            transform: translate(-50%, 0);
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-top: 0;
            width: 100%;
            pointer-events: none;
        }

        .avatar-center, .nickname {
            pointer-events: auto;
        }

        .avatar-center {
            width: 120px;
            height: 120px;
            border-radius: 50%;
            overflow: hidden;
            box-shadow: 0 4px 32px rgba(0, 0, 0, 0.25);
            background: rgba(255, 255, 255, 0.2);
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
        }

        .avatar-center img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        .nickname {
            margin-top: 16px;
            color: #fff;
            font-size: 1.2rem;
            background: none;
            border: none;
            text-align: center;
            cursor: pointer;
            padding: 4px 12px;
            border-radius: 15px;
            transition: background-color 0.2s;
        }

        .nickname:hover {
            background: rgba(255, 255, 255, 0.1);
        }

        .nickname:focus {
            outline: none;
            background: rgba(255, 255, 255, 0.15);
        }

        .subtitle-area {
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            bottom: 32%;
            min-height: 60px;
            width: calc(100% - 48px);
            max-width: 500px;
            padding: 18px 28px;
            border-radius: 18px;
            background: rgba(0, 0, 0, 0.25);
            backdrop-filter: blur(8px);
            color: #fff;
            font-size: 1.4rem;
            font-weight: 500;
            box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
            display: none;
            box-sizing: border-box;
            opacity: 0;
            transition: opacity 0.3s ease, transform 0.3s cubic-bezier(.4, 2, .6, 1);
            z-index: 20;
            max-height: 180px;
            overflow-x: hidden;
            overflow-y: auto;
            scrollbar-width: none;
            -ms-overflow-style: none;
        }

        .subtitle-area::-webkit-scrollbar {
            display: none;
        }

        .subtitle-list {
            display: flex;
            flex-direction: column;
            gap: 8px;
            width: 100%;
        }

        .subtitle-area.on {
            display: block;
            opacity: 1;
            transform: translateX(-50%) scale(1.04);
        }

        .subtitle-user {
            color: #00eaff;
            display: block;
            margin-bottom: 8px;
            font-size: 1.1em;
            opacity: 0.9;
        }

        .subtitle-ai {
            color: #ffd700;
            display: block;
            font-size: 1.1em;
            opacity: 0.9;
        }

        .subtitle-user:before {
            content: '你：';
            opacity: 0.6;
            font-size: 0.9em;
            margin-right: 8px;
        }

        .subtitle-ai:before {
            content: 'AI：';
            opacity: 0.6;
            font-size: 0.9em;
            margin-right: 8px;
        }

        .action-btn {
            display: flex;
            flex-direction: column;
            align-items: center;
            background: none;
            border: none;
            cursor: pointer;
            outline: none;
            transition: transform 0.15s;
        }

        .action-btn:active {
            transform: scale(0.92);
        }

        .action-btn svg {
            width: 48px;
            height: 48px;
            margin-bottom: 6px;
        }

        .action-label {
            color: #fff;
            font-size: 1rem;
            text-shadow: 0 1px 4px #0008;
        }

        @media (max-width: 600px) {
            .avatar-center {
                width: 80px;
                height: 80px;
            }

            .subtitle-area {
                font-size: 1rem;
                padding: 10px 12px;
                bottom: 40%;
            }

            .bottom-bar svg {
                width: 36px;
                height: 36px;
            }

            .nickname {
                font-size: 1rem;
            }

            .in-call-btns {
                gap: 16px;
            }

            .in-call-btns.split .action-btn:first-child {
                transform: translateX(-45px);
            }

            .in-call-btns.split .action-btn:last-child {
                transform: translateX(45px);
            }

            .fab-interrupt {
                width: 48px;
                height: 48px;
                min-width: 48px;
                min-height: 48px;
                flex-shrink: 0;
            }

            .fab-interrupt svg {
                width: 24px;
                height: 24px;
                min-width: 24px;
                min-height: 24px;
            }
        }

        .call-controls {
            position: absolute;
            left: 50%;
            transform: translate(-50%, 0);
            z-index: 10;
            transition: top 0.7s cubic-bezier(.4, 2, .6, 1), bottom 0.7s cubic-bezier(.4, 2, .6, 1), transform 0.7s cubic-bezier(.4, 2, .6, 1);
        }

        .call-controls.center {
            top: calc(18% + 120px + 40px);
            bottom: auto;
        }

        .call-controls.bottom {
            top: auto;
            bottom: 120px;
        }

        .call-btn-wrapper {
            display: flex;
            justify-content: center;
            align-items: center;
            transition: opacity 0.4s, transform 0.5s cubic-bezier(.4, 2, .6, 1);
        }

        .call-btn-wrapper.hide {
            opacity: 0;
            pointer-events: none;
            transform: scale(0.8);
        }

        .in-call-btns {
            display: flex;
            gap: 24px;
            opacity: 0;
            pointer-events: none;
            transition: opacity 0.4s, transform 0.5s cubic-bezier(.4, 2, .6, 1);
            align-items: center;
            justify-content: center;
        }

        .in-call-btns.show {
            opacity: 1;
            pointer-events: auto;
        }

        .in-call-btns .action-btn {
            transition: transform 0.6s cubic-bezier(.4, 2, .6, 1);
        }

        .in-call-btns.split .action-btn:first-child {
            transform: translateX(-60px);
        }

        .in-call-btns.split .action-btn:nth-child(2) {
            transform: translateX(0);
        }

        .in-call-btns.split .action-btn:last-child {
            transform: translateX(60px);
        }

        #interruptCallBtn.active svg {
            filter: drop-shadow(0 0 8px rgba(255, 193, 7, 0.5));
        }

        /* 呼叫中动画 */
        .calling-anim {
            animation: rotateCall 1s linear infinite;
        }

        @keyframes rotateCall {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }

        .avatar-center.talking-anim {
            animation: talkingBounce 0.7s infinite cubic-bezier(.4, 1.6, .6, 1.1);
        }

        @keyframes talkingBounce {
            0% {
                transform: scale(1);
            }
            20% {
                transform: scale(1.12);
            }
            50% {
                transform: scale(0.95);
            }
            80% {
                transform: scale(1.10);
            }
            100% {
                transform: scale(1);
            }
        }

        @media (max-width: 600px) {
            .call-controls.bottom {
                bottom: 160px;
            }
        }

        .modal-bg {
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.18);
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background 0.3s;
        }

        .modal-dialog {
            background: rgba(34, 34, 34, 0.92);
            border-radius: 22px;
            box-shadow: 0 2px 16px #0003;
            padding: 24px 20px 18px 20px;
            min-width: 260px;
            max-width: 90vw;
            display: flex;
            flex-direction: column;
            align-items: stretch;
        }

        .modal-title {
            color: #fff;
            font-size: 1.05rem;
            margin-bottom: 12px;
            text-align: center;
            font-weight: 400;
            letter-spacing: 1px;
        }

        .modal-input {
            padding: 8px 12px;
            border-radius: 8px;
            background: #555555;
            color: #ffffff;
            font-size: 1.05rem;
            margin-bottom: 18px;
            outline: none;
            box-shadow: 0 2px 10px #b2c2e022;
            transition: background 0.2s, border 0.2s, box-shadow 0.2s;
            text-align: center;
        }

        .modal-input:focus {
            background: #555555;
            border: 1.5px solid #b2ecf1;
            box-shadow: 0 4px 16px #00eaff22;
        }

        .modal-input:hover {
            border: 1.5px solid #8ecfff;
            background: #555555;
        }

        .modal-actions {
            display: flex;
            justify-content: center;
            gap: 0;
        }

        .modal-btn {
            padding: 7px 32px;
            border-radius: 8px;
            border: none;
            background: #00eaff;
            color: #222;
            font-size: 1rem;
            font-weight: 500;
            cursor: pointer;
            transition: background 0.2s;
            box-shadow: none;
        }

        .modal-btn:hover {
            background: #00bcd4;
        }

        .modal-btn.primary {
            background: #00eaff;
            color: #222;
            font-weight: 500;
        }

        .modal-btn.primary:hover {
            background: #00bcd4;
        }

        .toast-container {
            position: fixed;
            left: 50%;
            bottom: 48px;
            transform: translateX(-50%);
            z-index: 2000;
            display: flex;
            flex-direction: column;
            align-items: center;
            pointer-events: none;
        }

        .toast-message {
            background: rgba(34, 34, 34, 0.92);
            color: #fff;
            padding: 12px 32px;
            border-radius: 18px;
            margin-top: 8px;
            font-size: 1.05rem;
            box-shadow: 0 2px 12px #0003;
            opacity: 0;
            animation: toast-in 0.25s forwards, toast-out 0.3s 2.2s forwards;
            pointer-events: auto;
            min-width: 120px;
            text-align: center;
        }

        @keyframes toast-in {
            from {
                opacity: 0;
                transform: translateY(30px) scale(0.98);
            }
            to {
                opacity: 1;
                transform: translateY(0) scale(1);
            }
        }

        @keyframes toast-out {
            to {
                opacity: 0;
                transform: translateY(30px) scale(0.98);
            }
        }

        .floating-group {
            position: relative;
            margin-bottom: 18px;
        }

        .floating-label {
            position: absolute;
            left: 12px;
            top: 12px;
            color: #b2ecf1;
            font-size: 1.05rem;
            pointer-events: none;
            background: transparent;
            transition: all 0.2s;
            padding: 0 4px;
            z-index: 2;
        }

        .modal-input:focus + .floating-label,
        .modal-input:not(:placeholder-shown) + .floating-label {
            top: -10px;
            left: 8px;
            font-size: 0.85rem;
            color: #00eaff;
            background: #222;
            border-radius: 4px;
            padding: 0 4px;
        }

        .modal-input {
            width: 100%;
            box-sizing: border-box;
            background: #555555;
            color: #fff;
            border: 2px solid transparent; /* 保持2px宽度，防止focus时跳动 */
            border-radius: 8px;
            padding: 18px 12px 8px 12px;
            font-size: 1.4rem;
            outline: none;
            margin-bottom: 0;
            margin-top: 0;
            transition: border 0.2s, box-shadow 0.2s;
        }

        .modal-input:focus {
            border: 2px solid #b2ecf1;
            box-shadow: 0 4px 16px #00eaff22;
            background: #555555;
        }

        .modal-input:hover {
            border: 2px solid #8ecfff;
            background: #555555;
        }

        .fab-interrupt {
            position: relative;
            width: 56px;
            height: 56px;
            min-width: 56px;
            min-height: 56px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.15);
            border: none;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s ease;
            outline: none;
            opacity: 0;
            pointer-events: none;
            transform: scale(0.8);
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
            flex-shrink: 0;
        }

        .fab-interrupt.show {
            opacity: 1;
            pointer-events: auto;
            transform: scale(1);
        }

        .fab-interrupt:hover {
            background: rgba(255, 255, 255, 0.3);
            transform: scale(1.05);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        }

        .fab-interrupt:active {
            transform: scale(0.95);
        }

        .fab-interrupt.active svg {
            filter: drop-shadow(0 0 8px rgba(255, 193, 7, 0.5));
        }

        .fab-interrupt svg {
            width: 28px;
            height: 28px;
            min-width: 28px;
            min-height: 28px;
            fill: #ffd700;
            transition: transform 0.3s ease, filter 0.3s ease;
            flex-shrink: 0;
        }

        .fab-interrupt:hover svg {
            transform: scale(1.1);
        }
    </style>
</head>
<body>
<div class="bg-blur" id="bgBlur"></div>
<div class="main-content">
    <div class="top-bar">
        <button class="icon-btn" id="fullscreenToggle" title="全屏" style="margin-right:auto;">
            <svg viewBox="0 0 24 24" width="24" height="24">
                <polyline points="4,9 4,4 9,4" fill="none" stroke="#fff" stroke-width="2"/>
                <polyline points="20,9 20,4 15,4" fill="none" stroke="#fff" stroke-width="2"/>
                <polyline points="4,15 4,20 9,20" fill="none" stroke="#fff" stroke-width="2"/>
                <polyline points="20,15 20,20 15,20" fill="none" stroke="#fff" stroke-width="2"/>
            </svg>
        </button>
        <button class="icon-btn" id="subtitleToggle" title="字幕开关">
            <svg viewBox="0 0 24 24" width="24" height="24">
                <text x="4" y="17" font-size="13" font-family="Arial" fill="#fff">CC</text>
            </svg>
        </button>
        <button class="icon-btn" id="settingsBtn" title="设置">
            <svg viewBox="0 0 1067 1024" width="24" height="24">
                <path d="M697.590924 407.835192C686.946022 412.912319 682.423539 425.647804 687.479334 436.271372 698.42289 459.225109 703.969332 483.864111 703.969332 509.569735 703.969332 603.667255 627.407102 680.229484 533.309583 680.229484 439.212064 680.208152 362.649834 603.645922 362.649834 509.548403 362.649834 463.960917 380.398448 421.103987 412.631808 388.870627 444.843836 356.6586 487.700765 338.909986 533.266918 338.909986L533.309583 338.909986C558.737886 338.909986 583.227559 344.349765 606.031968 355.101329 616.719536 360.178457 629.433687 355.549311 634.425485 344.925742 639.438614 334.259508 634.894799 321.545356 624.249897 316.532226 595.685721 303.071438 565.094962 296.245049 533.309583 296.245049L533.266918 296.245049C476.309227 296.245049 422.743398 318.430816 382.467697 358.706516 342.170664 399.00355 319.984896 452.569379 319.984896 509.548403 319.984896 627.154302 415.682351 722.873089 533.309583 722.894422 650.915483 722.894422 746.634269 627.175634 746.634269 509.569735 746.634269 477.485702 739.701217 446.638952 726.027104 417.946782 720.949977 407.30188 708.257158 402.822062 697.590924 407.835192M1014.764201 603.912578C961.113042 625.223714 916.144199 663.920812 887.260036 713.902786 858.418538 763.88476 847.389652 822.165064 855.75198 879.33608 808.073912 920.891729 752.012184 953.317081 692.4306 973.774919 647.120437 937.893706 591.101373 918.267836 533.311716 918.267836 475.543391 918.267836 419.524328 937.893706 374.192832 973.774919 314.611247 953.317081 258.570852 920.891729 210.871453 879.33608 219.23378 822.186397 208.183562 763.906093 179.342064 713.902786 150.479234 663.920812 105.531722 625.245046 51.859231 603.912578 45.736813 572.639179 42.664938 540.960463 42.664938 509.559068 42.664938 478.136343 45.736813 446.457627 51.859231 415.184228 105.531722 393.851759 150.479234 355.175993 179.342064 305.194019 208.183562 255.190713 219.23378 196.910409 210.871453 139.760725 258.570852 98.205076 314.611247 65.779724 374.192832 45.321886 419.524328 81.203098 475.543391 100.828969 533.311716 100.828969 591.101373 100.828969 647.120437 81.203098 692.4306 45.321886 752.012184 65.779724 808.073912 98.205076 855.75198 139.760725 847.389652 196.93174 858.418538 255.212045 887.260036 305.194019 916.144199 355.175993 961.113042 393.873092 1014.764201 415.184228 1020.865287 446.414961 1023.958495 478.11501 1023.958495 509.559068 1023.958495 541.02446 1020.865287 572.681844 1014.764201 603.912578M1054.101273 394.72639C1052.52267 387.452018 1047.210886 381.521592 1040.171172 379.111023 991.191824 362.301037 949.998826 328.467742 924.229205 283.861551 898.480915 239.255358 889.777267 186.670823 899.696865 135.814218 901.126141 128.518514 898.672907 120.988152 893.16913 115.975022 836.275436 63.945131 767.499557 24.181409 694.32919 1.035681 687.204145-1.204228 679.439126 0.395707 673.871352 5.280843 634.79027 39.39146 584.872293 58.164032 533.311716 58.164032 481.75114 58.164032 431.833163 39.39146 392.752081 5.280843 387.162974 0.41704 379.419287-1.182896 372.294243 1.035681 299.102543 24.202742 230.347996 63.945131 173.454303 115.975022 167.971858 120.988152 165.497292 128.518514 166.926568 135.814218 176.867498 186.649491 168.142518 239.234026 142.372896 283.861551 116.624606 328.467742 75.474274 362.301037 26.452261 379.111023 19.391214 381.542924 14.122094 387.452018 12.543492 394.72639 4.202496 432.655519 0 471.28862 0 509.559068 0 547.829518 4.202496 586.462618 12.543492 624.370415 14.122094 631.666119 19.391214 637.553881 26.452261 639.985782 75.474274 656.795767 116.624606 690.629062 142.372896 735.235255 168.142518 779.862779 176.867498 832.447315 166.926568 883.30392 165.497292 890.578291 167.971858 898.108653 173.454303 903.121783 230.347996 955.151674 299.102543 994.894063 372.294243 1018.061124 374.384825 1018.72243 376.582069 1019.06375 378.736648 1019.06375 383.813776 1019.06375 388.805574 1017.25049 392.752081 1013.815963 431.833163 979.705346 481.772472 960.932772 533.311716 960.932772 584.872293 960.932772 634.79027 979.705346 673.871352 1013.815963 679.460459 1018.701098 687.24681 1020.343698 694.32919 1018.061124 767.499557 994.915395 836.275436 955.173006 893.16913 903.121783 898.672907 898.108653 901.126141 890.578291 899.696865 883.30392 889.777267 832.425982 898.480915 779.841447 924.229205 735.235255 949.998826 690.629062 991.191824 656.795767 1040.171172 639.985782 1047.210886 637.575214 1052.52267 631.666119 1054.101273 624.370415 1062.399604 586.526615 1066.623433 547.893515 1066.623433 509.559068 1066.623433 471.224623 1062.399604 432.591522 1054.101273 394.72639"
                      fill="#fff"/>
            </svg>
        </button>
    </div>
    <div class="avatar-section">
        <div class="avatar-center" id="avatarCenter">
            <img id="avatarImg" src="avatar.png" alt="头像"/>
        </div>
        <div class="nickname" id="nickname" contenteditable="true">静香</div>
    </div>
    <div class="subtitle-area" id="subtitleArea">
        <div class="subtitle-list" id="subtitleList"></div>
    </div>
    <div class="call-controls" id="callControls">
        <!-- 初始状态：只显示呼叫按钮 -->
        <div class="call-btn-wrapper" id="callBtnWrapper">
            <button class="action-btn" id="callBtn">
                <svg id="callIcon" viewBox="0 0 24 24">
                    <path fill="#00eaff"
                          d="M6.62 10.79a15.05 15.05 0 0 0 6.59 6.59l2.2-2.2a1 1 0 0 1 1.01-.24c1.12.37 2.33.57 3.58.57a1 1 0 0 1 1 1V20a1 1 0 0 1-1 1C10.07 21 3 13.93 3 5a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1c0 1.25.2 2.46.57 3.58a1 1 0 0 1-.24 1.01l-2.2 2.2z"/>
                </svg>
                <span class="action-label" id="callLabel">呼叫</span>
            </button>
        </div>
        <!-- 通话中状态：显示挂断、打断和麦克风按钮 -->
        <div class="in-call-btns" id="inCallBtns">
            <button class="action-btn" id="hangupBtn">
                <svg id="hangupIcon" viewBox="0 0 24 24">
                    <path fill="#ff4d4f"
                          d="M21 15.46l-5.27-2.11a1 1 0 0 0-1.11.21l-2.1 2.1a16.06 16.06 0 0 1-6.59-6.59l2.1-2.1a1 1 0 0 0 .21-1.11L8.54 3A1 1 0 0 0 7.46 2H4a1 1 0 0 0-1 1C3 13.93 10.07 21 20 21a1 1 0 0 0 1-1v-3.46a1 1 0 0 0-.99-1.08z"/>
                </svg>
                <span class="action-label" id="hangupLabel">挂断</span>
            </button>
            <button class="fab-interrupt" id="fabInterruptBtn" title="打断">
                <svg viewBox="0 0 24 24">
                    <rect x="6" y="6" width="4" height="12" rx="2"/>
                    <rect x="14" y="6" width="4" height="12" rx="2"/>
                </svg>
            </button>
            <button class="action-btn" id="micBtn">
                <svg id="micIcon" viewBox="0 0 24 24">
                    <path fill="#fff"
                          d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z"/>
                </svg>
                <span class="action-label" id="micLabel">麦克风已开</span>
            </button>
        </div>
    </div>
</div>
<div class="modal-bg" id="settingsModalBg" style="display:none;">
    <div class="modal-dialog">
        <div class="modal-title" id="modalTitle">设置</div>
        <div class="floating-group">
            <input type="text" id="wsUrlInput" class="modal-input" placeholder=" " autocomplete="off"/>
            <label for="wsUrlInput" class="floating-label">服务地址</label>
        </div>
        <div class="floating-group">
            <input type="text" id="securityCodeInput" class="modal-input" placeholder=" " style="margin-top:8px;"
                   autocomplete="off"/>
            <label for="securityCodeInput" class="floating-label">安全码</label>
        </div>
        <div class="modal-actions">
            <button id="modalOk" class="modal-btn primary">保存</button>
        </div>
    </div>
</div>
<div id="toastContainer" class="toast-container"></div>
<input type="file" id="avatarInput" accept="image/*" style="display:none;"/>
<script>
    // 变量定义
    let ws = null;
    let wsShouldShowError = false;
    let audioContext = null;
    let audioSource = null;
    let scriptNode = null;
    let mediaStream = null;
    let isRecording = false;
    let isMicOn = true;
    let isCalling = false;
    let permissionStatus = null;
    let audioContextForPlayback = null;
    let audioQueue = [];
    let isAudioPlaying = false;
    let lastUserText = '';
    let lastAIText = '';
    let subtitleOn = false;
    let avatarUrl = 'avatar.png';
    // 打断功能相关变量
    let isAISpeaking = false;
    let pcmBuffer = [];
    let sendInterval = null;
    // WebSocket地址设置相关
    const DEFAULT_WS_URL = `wss://${window.location.host}/ws`;
    let wsUrl = localStorage.getItem('wsUrl') || DEFAULT_WS_URL;
    // 新增状态机
    let callState = 'idle'; // idle, calling, inCall
    // 新增：专门用于打断按钮的状态
    let interruptEnabled = false;

    // DOM元素
    const callControls = document.getElementById('callControls');
    const callBtnWrapper = document.getElementById('callBtnWrapper');
    const inCallBtns = document.getElementById('inCallBtns');
    const callBtn = document.getElementById('callBtn');
    const callIcon = document.getElementById('callIcon');
    const callLabel = document.getElementById('callLabel');
    const hangupBtn = document.getElementById('hangupBtn');
    const micBtn = document.getElementById('micBtn');
    const micIcon = document.getElementById('micIcon');
    const micLabel = document.getElementById('micLabel');
    const subtitleToggle = document.getElementById('subtitleToggle');
    const subtitleArea = document.getElementById('subtitleArea');
    const avatarImg = document.getElementById('avatarImg');
    const avatarInput = document.getElementById('avatarInput');
    const bgBlur = document.getElementById('bgBlur');
    const avatarCenter = document.getElementById('avatarCenter');
    const nickname = document.getElementById('nickname');
    const fullscreenToggle = document.getElementById('fullscreenToggle');
    const settingsBtn = document.getElementById('settingsBtn');
    const settingsModalBg = document.getElementById('settingsModalBg');
    const wsUrlInput = document.getElementById('wsUrlInput');
    const securityCodeInput = document.getElementById('securityCodeInput');
    const modalOk = document.getElementById('modalOk');
    const toastContainer = document.getElementById('toastContainer');
    const fabInterruptBtn = document.getElementById('fabInterruptBtn');

    // 昵称编辑逻辑
    nickname.addEventListener('blur', function () {
        if (!this.textContent.trim()) {
            this.textContent = '静香';
        }
    });

    nickname.addEventListener('keydown', function (e) {
        if (e.key === 'Enter') {
            e.preventDefault();
            this.blur();
        }
    });

    // 字幕开关逻辑
    subtitleToggle.onclick = function () {
        subtitleOn = !subtitleOn;
        if (subtitleOn) {
            subtitleArea.classList.add('on');
            subtitleToggle.classList.add('active');
            updateSubtitle();
        } else {
            subtitleArea.classList.remove('on');
            subtitleToggle.classList.remove('active');
            setTimeout(() => {
                if (!subtitleOn) {
                    const list = document.getElementById('subtitleList');
                    if (list) list.innerHTML = '';
                }
            }, 300);
        }
    };

    // 头像更换逻辑
    avatarCenter.onclick = function () {
        avatarInput.click();
    };
    avatarInput.onchange = function (e) {
        const file = e.target.files[0];
        if (!file) return;
        const reader = new FileReader();
        reader.onload = function (evt) {
            avatarUrl = evt.target.result;
            avatarImg.src = avatarUrl;
            bgBlur.style.background = `url('${avatarUrl}') center/cover no-repeat`;
        };
        reader.readAsDataURL(file);
    };

    // 按钮状态切换动画
    function updateCallControls() {
        if (callState === 'idle') {
            callControls.classList.add('center');
            callControls.classList.remove('bottom');
            callBtnWrapper.classList.remove('hide');
            inCallBtns.classList.remove('show', 'split');
            callBtn.disabled = false;
            callIcon.classList.remove('calling-anim');
            callLabel.textContent = '呼叫';
            callLabel.style.color = '#00eaff';
        } else if (callState === 'calling') {
            callControls.classList.add('center');
            callControls.classList.remove('bottom');
            callBtnWrapper.classList.remove('hide');
            inCallBtns.classList.remove('show', 'split');
            callBtn.disabled = true;
            callIcon.classList.add('calling-anim');
            callLabel.textContent = '呼叫中...';
            callLabel.style.color = '#ffd700';
        } else if (callState === 'inCall-pre') {
            // 下移到底部但不分裂
            callControls.classList.remove('center');
            callControls.classList.add('bottom');
            callBtnWrapper.classList.add('hide');
            inCallBtns.classList.add('show');
            inCallBtns.classList.remove('split');
            callBtn.disabled = false;
            callIcon.classList.remove('calling-anim');
        } else if (callState === 'inCall') {
            // 分裂动画
            callControls.classList.remove('center');
            callControls.classList.add('bottom');
            callBtnWrapper.classList.add('hide');
            inCallBtns.classList.add('show', 'split');
            callBtn.disabled = false;
            callIcon.classList.remove('calling-anim');
        }
    }

    // 初始只显示呼叫按钮
    updateCallControls();

    // 动画统一函数 direction: 'down'（呼叫） | 'up'（挂断）
    function animateCallTransition(direction, callback) {
        if (direction === 'down') {
            // 呼叫：下移到底部，再分裂
            callControls.classList.remove('center');
            callControls.classList.add('bottom');
            callBtnWrapper.classList.add('hide');
            inCallBtns.classList.add('show');
            setTimeout(() => {
                inCallBtns.classList.add('split');
                if (callback) setTimeout(callback, 300); // 分裂动画
            }, 400); // 下移动画
        } else {
            // 挂断：先合并，再淡出/淡入，再上移
            inCallBtns.classList.remove('split');
            setTimeout(() => {
                // 两按钮淡出，呼叫按钮淡入（都在底部）
                inCallBtns.classList.remove('show');
                callBtnWrapper.classList.remove('hide');
                // 修正：动画期间直接设置呼叫按钮内容为"呼叫"
                callLabel.textContent = '呼叫';
                callLabel.style.color = '#00eaff';
                callIcon.classList.remove('calling-anim');
                setTimeout(() => {
                    // 只有呼叫按钮时整体上移
                    callControls.classList.remove('bottom');
                    callControls.classList.add('center');
                    if (callback) setTimeout(callback, 400); // 上移动画
                }, 300); // 淡出/淡入动画
            }, 250); // 合并动画
        }
    }

    // 显示加载状态
    function showLoading(message = '处理中...') {
        const loadingToast = document.createElement('div');
        loadingToast.className = 'toast-message';
        loadingToast.textContent = message;
        loadingToast.style.background = 'rgba(0, 234, 255, 0.9)';
        loadingToast.style.color = '#222';
        toastContainer.appendChild(loadingToast);
        return loadingToast;
    }

    // 隐藏加载状态
    function hideLoading(loadingToast) {
        if (loadingToast && loadingToast.parentNode) {
            loadingToast.remove();
        }
    }

    // 呼叫按钮点击
    callBtn.onclick = async function () {
        // 新增：检查安全码
        const securityCode = localStorage.getItem('securityCode') || '';
        if (!securityCode) {
            showSecurityCodeOnlyModal();
            return;
        }

        // 显示加载状态
        const loadingToast = showLoading('验证安全码...');

        // 新增：先校验安全码
        try {
            const resp = await fetch('/api/chat/checkSecurity?code=' + encodeURIComponent(securityCode));
            const result = await resp.text();
            if (result !== 'ok') {
                hideLoading(loadingToast);
                showToast('安全码无效，请重新输入');
                localStorage.removeItem('securityCode');
                showSecurityCodeOnlyModal();
                return;
            }
        } catch (e) {
            hideLoading(loadingToast);
            showToast('网络连接失败，请检查网络');
            return;
        }

        hideLoading(loadingToast);

        if (callState !== 'idle') return;
        callState = 'calling';
        updateCallControls();
        // 仅显示呼叫中动画，不下移和分裂，等待ws连接结果
        startCall();
    };
    // 挂断按钮点击
    hangupBtn.onclick = function () {
        if (callState !== 'inCall') return;
        stopCall();
        animateCallTransition('up', () => {
            callState = 'idle';
            updateCallControls();
        });
    };
    // 麦克风按钮逻辑
    micBtn.onclick = function () {
        if (callState !== 'inCall') return; // 仅通话中可切换
        isMicOn = !isMicOn;
        updateMicUI();
        if (ws && ws.readyState === WebSocket.OPEN) {
            if (isMicOn) {
                startRecording(); // 重新开始录音
            } else {
                stopRecording(); // 停止录音
            }
        }
    };


    function updateMicUI() {
        if (isMicOn) {
            micIcon.innerHTML = '<path fill="#fff" d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z"/>';
            micLabel.textContent = '麦克风已开';
            micLabel.style.color = '#fff';
        } else {
            micIcon.innerHTML = '<path fill="#ff4d4f" d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.91-3c-.49 0-.9.36-.98.85C16.52 14.2 14.47 16 12 16s-4.52-1.8-4.93-4.15c-.08-.49-.49-.85-.98-.85-.61 0-1.09.54-1 1.14.49 3 2.89 5.35 5.91 5.78V20c0 .55.45 1 1 1s1-.45 1-1v-2.08c3.02-.43 5.42-2.78 5.91-5.78.1-.6-.39-1.14-1-1.14z"/>';
            micLabel.textContent = '麦克风已关';
            micLabel.style.color = '#ff4d4f';
        }
    }

    // 打断AI功能
    function interruptAI() {
        if (!interruptEnabled) return;

        // 停止当前正在播放的音频源
        if (audioContextForPlayback && audioContextForPlayback.state !== 'closed') {
            if (window.currentAudioSource) {
                window.currentAudioSource.stop();
                window.currentAudioSource = null;
            }
        }

        // 清空音频队列
        audioQueue = [];
        isAudioPlaying = false;

        // 移除头像说话动画
        avatarCenter.classList.remove('talking-anim');

        // 发送打断信号到服务器
        if (ws && ws.readyState === WebSocket.OPEN) {
            try {
                ws.send(JSON.stringify({type: 'interrupt'}));
            } catch (e) {
                showToast('网络连接异常，请重试');
            }
        }

        // 停止字幕继续渲染（不清空已有内容，只是停止继续渲染）
        streamingAIIndex = null;

        // 更新状态
        isAISpeaking = false;
        interruptEnabled = false;
        updateInterruptUI();

        // 打断后先停止录音，然后重新开始录音（如果麦克风是开启状态）
        if (isMicOn && callState === 'inCall') {
            // 先停止录音，发送stop消息
            stopRecording();
            // 延迟一点时间再重新开始录音，确保后端ASR服务状态正确
            setTimeout(() => {
                if (isMicOn && callState === 'inCall') {
                    startRecording();
                }
            }, 100);
        }
    }

    // 更新打断按钮UI
    function updateInterruptUI() {
        if (interruptEnabled && callState === 'inCall') {
            fabInterruptBtn.classList.add('show');
        } else {
            fabInterruptBtn.classList.remove('show');
        }
    }

    fabInterruptBtn.onclick = function () {
        if (interruptEnabled) interruptAI();
    };


    // 防抖函数，用于优化频繁调用
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // 节流函数，用于限制函数调用频率
    function throttle(func, limit) {
        let inThrottle;
        return function () {
            const args = arguments;
            const context = this;
            if (!inThrottle) {
                func.apply(context, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
            }
        }
    }

    // 优化后的字幕更新函数，使用防抖
    const debouncedUpdateSubtitle = debounce(updateSubtitle, 100);

    // 字幕内容更新
    let dialogHistory = [];
    let streamingAIIndex = null;

    function addDialog(role, text, opts = {}) {
        if (role === 'ai' && opts.stream) {
            // 流式AI回复，更新最后一条AI消息
            if (streamingAIIndex === null) {
                dialogHistory.push({role, text});
                streamingAIIndex = dialogHistory.length - 1;
            } else {
                dialogHistory[streamingAIIndex].text = text;
            }
        } else if (role === 'ai' && opts.final) {
            // AI最终回复，streamingAIIndex归零
            if (streamingAIIndex !== null) {
                dialogHistory[streamingAIIndex].text = text;
                streamingAIIndex = null;
            } else {
                dialogHistory.push({role, text});
            }
        } else {
            dialogHistory.push({role, text});
            streamingAIIndex = null;
        }
        // 使用防抖更新字幕，避免频繁DOM操作
        debouncedUpdateSubtitle();
    }

    function updateSubtitle() {
        const list = document.getElementById('subtitleList');
        if (!list) return; // 防止报错
        if (!subtitleOn) {
            list.innerHTML = '';
            return;
        }
        list.innerHTML = dialogHistory.map(item => {
            if (item.role === 'user') {
                return `<div class="subtitle-user">${item.text}</div>`;
            } else {
                return `<div class="subtitle-ai">${item.text}</div>`;
            }
        }).join('');
        setTimeout(() => {
            if (list.parentElement) {
                list.parentElement.scrollTop = list.parentElement.scrollHeight;
            }
        }, 0);
    }

    // 权限检测
    async function checkMicrophonePermission() {
        try {
            if (navigator.permissions && navigator.permissions.query) {
                permissionStatus = await navigator.permissions.query({name: 'microphone'});
                return permissionStatus.state;
            }
            return 'unknown';
        } catch (e) {
            return 'unknown';
        }
    }

    // WebSocket相关
    function startCall() {
        if (isCalling) return;
        wsShouldShowError = true;
        // 新增：带上安全码参数
        const securityCode = localStorage.getItem('securityCode') || '';
        let wsConnectUrl = wsUrl;
        if (securityCode) {
            wsConnectUrl += `?security=${encodeURIComponent(securityCode)}`;
        }
        ws = new WebSocket(wsConnectUrl);
        ws.onopen = () => {
            isCalling = true;
            wsShouldShowError = false;
            animateCallTransition('down', () => {
                callState = 'inCall';
                updateCallControls();
                startRecording();
            });
        };
        ws.onclose = (event) => {
            isCalling = false;
            updateCallControls();
            stopRecording();
            // 新增：处理安全码无效
            if (event && event.reason && event.reason.indexOf('security') !== -1) {
                showToast('安全码无效，请重新输入');
                localStorage.removeItem('securityCode');
            } else if (wsShouldShowError) {
                showToast('通话连接失败，请检查网络或服务端状态');
            }
            callState = 'idle';
            updateCallControls();
        };
        ws.onerror = () => {
            isCalling = false;
            updateCallControls();
            stopRecording();
            if (wsShouldShowError) {
                showToast('通话连接失败，请检查网络或服务端状态');
                wsShouldShowError = false;
            }
            callState = 'idle';
            updateCallControls();
        };
        ws.onmessage = async (event) => {
            if (event.data instanceof Blob) {
                // AI语音回复
                const arrayBuffer = await event.data.arrayBuffer();
                const audioData = new Int16Array(arrayBuffer);
                audioQueue.push(audioData);

                // 收到音频数据时显示打断按钮
                if (!interruptEnabled && callState === 'inCall') {
                    interruptEnabled = true;
                    updateInterruptUI();
                }

                if (!isAudioPlaying) {
                    await processAudioQueue();
                }
            } else {
                const data = JSON.parse(event.data);
                if (data.type === 'asr') {
                    lastUserText = data.text;
                    if (data.isEnd) {
                        lastUserText = data.text;
                        addDialog('user', lastUserText);
                    }
                } else if (data.type === 'chat') {
                    lastAIText = data.text;
                    if (data.isFinal) {
                        addDialog('ai', lastAIText, {final: true});
                    } else {
                        addDialog('ai', lastAIText, {stream: true});
                    }
                } else if (data.type === 'dialog_start') {
                    // 收到单次对话开始，暂停麦克风，显示打断按钮
                    stopRecording();
                    interruptEnabled = true;
                    updateInterruptUI();
                } else if (data.type === 'dialog_end') {
                    // 收到单次对话结束，恢复麦克风，但不立即隐藏打断按钮
                    // 打断按钮将在音频播放完毕后自动隐藏
                    if (isMicOn && callState === 'inCall') {
                        await startRecording();
                    }
                }
            }
        }
    }

    function stopCall() {
        isRecording = false; // 确保录音状态被重置
        if (ws) {
            wsShouldShowError = false;
            ws.close();
            ws = null;
        }
        isCalling = false;
        isMicOn = true; // 挂断后自动重置麦克风为开
        updateMicUI();
        updateCallControls();
        stopRecording();
        lastUserText = '';
        lastAIText = '';
        dialogHistory = [];
        streamingAIIndex = null;
        // 挂断时关闭字幕
        subtitleOn = false;
        subtitleArea.classList.remove('on');
        subtitleToggle.classList.remove('active');
        const list = document.getElementById('subtitleList');
        if (list) list.innerHTML = '';
        // 重置打断相关状态
        isAISpeaking = false;
        interruptEnabled = false;
        audioQueue = [];
        isAudioPlaying = false;
        updateInterruptUI();

        // 重置AudioWorklet注册标志，以便下次通话时重新注册
        window.audioProcessorRegistered = false;
    }

    // 录音相关
    async function startRecording() {
        if (!isMicOn) return;
        const permissionState = await checkMicrophonePermission();
        if (permissionState === 'denied') {
            showToast('麦克风权限被拒绝，请检查浏览器设置');
            return;
        }
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send("start");
        }
        const stream = await navigator.mediaDevices.getUserMedia({
            audio: {
                echoCancellation: true,
                noiseSuppression: true,
                autoGainControl: true,
                channelCount: 1,
                sampleRate: 16000
            }
        });
        if (!audioContext) {
            audioContext = new (window.AudioContext || window.webkitAudioContext)({sampleRate: 16000});
        }
        audioSource = audioContext.createMediaStreamSource(stream);
        mediaStream = stream;

        // 检查AudioWorklet支持
        const supportsAudioWorklet = audioContext.audioWorklet && typeof AudioWorkletNode !== 'undefined';

        // 使用AudioWorkletNode替代ScriptProcessorNode
        if (supportsAudioWorklet) {
            try {
                // 检查处理器模块是否已经全局注册
                if (!window.audioWorkletModuleLoaded) {
                    // 创建音频处理模块
                    const audioWorkletCode = `
                        class AudioProcessor extends AudioWorkletProcessor {
                            process(inputs, outputs, parameters) {
                                const input = inputs[0];
                                const output = outputs[0];
                                
                                if (input.length > 0) {
                                    const inputChannel = input[0];
                                    const outputChannel = output[0];
                                    
                                    for (let i = 0; i < inputChannel.length; i++) {
                                        outputChannel[i] = inputChannel[i];
                                    }
                                    
                                    // 发送音频数据到主线程
                                    this.port.postMessage({
                                        audioData: inputChannel.slice()
                                    });
                                }
                                
                                return true;
                            }
                        }
                        try {
                            registerProcessor('audio-processor', AudioProcessor);
                        } catch (e) {
                            // 已注册则忽略
                        }
                    `;

                    // 创建Blob URL并加载AudioWorklet
                    const blob = new Blob([audioWorkletCode], {type: 'application/javascript'});
                    const url = URL.createObjectURL(blob);
                    await audioContext.audioWorklet.addModule(url);
                    URL.revokeObjectURL(url);

                    // 标记处理器模块已注册
                    window.audioWorkletModuleLoaded = true;
                }

                // 创建AudioWorkletNode
                const workletNode = new AudioWorkletNode(audioContext, 'audio-processor', {
                    numberOfInputs: 1,
                    numberOfOutputs: 1,
                    channelCount: 1,
                    processorOptions: {}
                });

                // 处理音频数据
                workletNode.port.onmessage = (event) => {
                    if (event.data.audioData && isRecording && isMicOn && !isAISpeaking) {
                        pcmBuffer.push(...event.data.audioData);
                    }
                };

                // 连接音频节点
                audioSource.connect(workletNode);
                scriptNode = workletNode; // 保存引用以便后续断开连接

            } catch (error) {
                const bufferSize = 4096;
                scriptNode = audioContext.create(bufferSize, 1, 1);
                scriptNode.onaudioprocess = (audioProcessingEvent) => {
                    if (!isRecording || !isMicOn) return;
                    const inputData = audioProcessingEvent.inputBuffer.getChannelData(0);
                    const pcmData = new Int16Array(inputData.length);
                    for (let i = 0; i < inputData.length; i++) {
                        pcmData[i] = Math.max(-1, Math.min(1, inputData[i])) * 0x7FFF;
                    }

                    // AI说话时不发送音频数据，避免回声
                    if (ws && ws.readyState === WebSocket.OPEN && !isAISpeaking) {
                        ws.send(pcmData.buffer);
                    }
                };
                audioSource.connect(scriptNode);
                scriptNode.connect(audioContext.destination);
            }

        } else {
            // 降级到ScriptProcessorNode
            const bufferSize = 4096;
            scriptNode = audioContext.create(bufferSize, 1, 1);
            scriptNode.onaudioprocess = (audioProcessingEvent) => {
                if (!isRecording || !isMicOn) return;
                const inputData = audioProcessingEvent.inputBuffer.getChannelData(0);
                const pcmData = new Int16Array(inputData.length);
                for (let i = 0; i < inputData.length; i++) {
                    pcmData[i] = Math.max(-1, Math.min(1, inputData[i])) * 0x7FFF;
                }

                // AI说话时不发送音频数据，避免回声
                if (ws && ws.readyState === WebSocket.OPEN && !isAISpeaking) {
                    ws.send(pcmData.buffer);
                }
            };
            audioSource.connect(scriptNode);
            scriptNode.connect(audioContext.destination);
        }

        isRecording = true;
        // 启动音频缓冲定时发送
        if (!sendInterval) {
            sendInterval = setInterval(() => {
                if (pcmBuffer.length > 0 && ws && ws.readyState === WebSocket.OPEN) {
                    const pcmData = new Int16Array(pcmBuffer.length);
                    for (let i = 0; i < pcmBuffer.length; i++) {
                        pcmData[i] = Math.max(-1, Math.min(1, pcmBuffer[i])) * 0x7FFF;
                    }
                    ws.send(pcmData.buffer);
                    pcmBuffer = [];
                }
            }, 200);
        }
    }

    function stopRecording() {
        isRecording = false;
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send("stop");
        }
        if (mediaStream) {
            mediaStream.getTracks().forEach(track => track.stop());
            mediaStream = null;
        }
        if (audioSource) {
            audioSource.disconnect();
            audioSource = null;
        }
        if (scriptNode) {
            scriptNode.disconnect();
            // 如果是AudioWorkletNode，需要关闭port
            if (scriptNode.port) {
                scriptNode.port.close();
            }
            scriptNode = null;
        }
        if (sendInterval) {
            clearInterval(sendInterval);
            sendInterval = null;
        }
    }

    // AI语音播放及头像波动动画
    async function playAudioFrame(audioData) {
        return new Promise((resolve) => {
            try {
                if (!audioContextForPlayback) {
                    audioContextForPlayback = new (window.AudioContext || window.webkitAudioContext)({sampleRate: 16000});
                }
                // 头像弹跳动画
                avatarCenter.classList.add('talking-anim');
                isAISpeaking = true;

                let int16Data = audioData instanceof Int16Array ? audioData : new Int16Array(audioData);
                const float32Data = new Float32Array(int16Data.length);
                for (let i = 0; i < int16Data.length; i++) {
                    float32Data[i] = int16Data[i] / 32768.0;
                }
                const audioBuffer = audioContextForPlayback.createBuffer(1, float32Data.length, 16000);
                audioBuffer.getChannelData(0).set(float32Data);
                const source = audioContextForPlayback.createBufferSource();
                source.buffer = audioBuffer;
                source.playbackRate.value = 1.0;
                source.connect(audioContextForPlayback.destination);

                // 保存当前播放的音频源引用，以便打断时能够停止它
                window.currentAudioSource = source;

                isAudioPlaying = true;
                source.start();
                source.onended = () => {
                    isAudioPlaying = false;
                    avatarCenter.classList.remove('talking-anim');
                    isAISpeaking = false;
                    // 清除音频源引用
                    if (window.currentAudioSource === source) {
                        window.currentAudioSource = null;
                    }
                    resolve();
                };
            } catch (e) {
                avatarCenter.classList.remove('talking-anim');
                isAISpeaking = false;
                resolve();
            }
        });
    }

    async function processAudioQueue() {
        while (audioQueue.length > 0 && interruptEnabled) {
            const audioData = audioQueue.shift();
            await playAudioFrame(audioData);
        }
        // 确保所有音频播放完毕后更新状态
        if (audioQueue.length === 0 && !isAudioPlaying) {
            isAISpeaking = false;
            // 音频播放完毕，隐藏打断按钮
            interruptEnabled = false;
            updateInterruptUI();
        }
    }

    // 全屏切换逻辑
    fullscreenToggle.onclick = function () {
        if (!document.fullscreenElement) {
            document.documentElement.requestFullscreen();
        } else {
            document.exitFullscreen();
        }
    };

    function showToast(msg) {
        const toast = document.createElement('div');
        toast.className = 'toast-message';
        toast.textContent = msg;
        toastContainer.appendChild(toast);
        setTimeout(() => {
            toast.remove();
        }, 2500);
    }

    modalOk.onclick = function () {
        let val = wsUrlInput.value.trim();
        let code = securityCodeInput.value.trim();
        // 如果wsUrl输入框被隐藏，说明只需要安全码
        if (wsUrlInput.parentElement.style.display === 'none') {
            if (!code) {
                showToast('请输入安全码');
                return;
            }
            localStorage.setItem('securityCode', code);
            settingsModalBg.style.display = 'none';
            showToast('安全码已保存');
            wsUrlInput.parentElement.style.display = '';
            // 恢复弹框标题和按钮
            document.getElementById('modalTitle').textContent = '设置';
            modalOk.textContent = '保存';
            return;
        }
        if (!val.startsWith('wss://') && !val.startsWith('ws://')) {
            showToast('请输入正确的服务地址（以 ws:// 或 wss:// 开头）');
            return;
        }
        if (!code) {
            showToast('请输入安全码');
            return;
        }
        wsUrl = val;
        localStorage.setItem('wsUrl', wsUrl);
        localStorage.setItem('securityCode', code);
        settingsModalBg.style.display = 'none';
        showToast('服务地址和安全码已保存，下次呼叫生效');
    };
    settingsModalBg.onclick = function (e) {
        if (e.target === settingsModalBg) settingsModalBg.style.display = 'none';
    };
    wsUrlInput.onkeydown = function (e) {
        if (e.key === 'Enter') modalOk.onclick();
    };
    wsUrlInput.oninput = function () {
        wsUrl = wsUrlInput.value.trim();
        localStorage.setItem('wsUrl', wsUrl);
    };
    securityCodeInput.oninput = function () {
        localStorage.setItem('securityCode', securityCodeInput.value.trim());
    };

    // 只弹出安全码输入框弹窗
    function showSecurityCodeOnlyModal() {
        wsUrlInput.parentElement.style.display = 'none';
        securityCodeInput.parentElement.style.display = '';
        securityCodeInput.value = '';
        settingsModalBg.style.display = 'flex';
        document.getElementById('modalTitle').textContent = '请输入安全码';
        modalOk.textContent = '确定';
        setTimeout(() => securityCodeInput.focus(), 100);
    }

    // 设置弹框恢复
    settingsBtn.onclick = function () {
        wsUrlInput.value = wsUrl;
        securityCodeInput.value = localStorage.getItem('securityCode') || '';
        wsUrlInput.parentElement.style.display = '';
        securityCodeInput.parentElement.style.display = '';
        settingsModalBg.style.display = 'flex';
        document.getElementById('modalTitle').textContent = '设置';
        modalOk.textContent = '保存';
        setTimeout(() => wsUrlInput.focus(), 100);
    };
    // 优化：弹窗关闭时立即恢复wsUrlInput显示，并恢复标题和按钮
    settingsModalBg.addEventListener('transitionend', () => {
        if (settingsModalBg.style.display === 'none') {
            wsUrlInput.parentElement.style.display = '';
            document.getElementById('modalTitle').textContent = '设置';
            modalOk.textContent = '保存';
        }
    });
    // 移除重复的事件监听器，统一处理弹窗关闭
    settingsModalBg.addEventListener('click', (e) => {
        if (e.target === settingsModalBg) {
            settingsModalBg.style.display = 'none';
            wsUrlInput.parentElement.style.display = '';
            document.getElementById('modalTitle').textContent = '设置';
            modalOk.textContent = '保存';
        }
    });

    // 资源清理函数
    function cleanupResources() {
        // 清理音频相关资源
        if (audioContext && audioContext.state !== 'closed') {
            audioContext.close();
            audioContext = null;
        }
        if (audioContextForPlayback && audioContextForPlayback.state !== 'closed') {
            audioContextForPlayback.close();
            audioContextForPlayback = null;
        }
        if (mediaStream) {
            mediaStream.getTracks().forEach(track => track.stop());
            mediaStream = null;
        }
        if (sendInterval) {
            clearInterval(sendInterval);
            sendInterval = null;
        }
        // 清理WebSocket连接
        if (ws) {
            ws.close();
            ws = null;
        }
        // 清理全局变量
        window.currentAudioSource = null;
        window.audioProcessorRegistered = false;
    }

    // 页面卸载时清理资源
    window.addEventListener('beforeunload', cleanupResources);
    window.addEventListener('pagehide', cleanupResources);

    // 初始化函数，统一管理所有初始化逻辑
    function initializeApp() {
        isMicOn = true;
        updateCallControls();
        updateMicUI();
        updateInterruptUI();
        updateSubtitle();
    }

    // 初始化
    initializeApp();

    // 页面健壮性优化：防止未加载完DOM时报错
    window.addEventListener('DOMContentLoaded', initializeApp);
</script>
</body>
</html>
